package com.java2nb.novel.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.java2nb.novel.pay.common.SignUtils;
import com.java2nb.novel.pay.dto.PayNotifyRequestDTO;
import com.java2nb.novel.pay.entity.MchAppConfig;
import com.java2nb.novel.pay.entity.PayOrder;
import com.java2nb.novel.pay.mapper.MchAppConfigDynamicSqlSupport;
import com.java2nb.novel.pay.mapper.MchAppConfigMapper;
import com.java2nb.novel.pay.mapper.PayOrderDynamicSqlSupport;
import com.java2nb.novel.pay.mapper.PayOrderMapper;
import com.java2nb.novel.pay.service.PayNotifyService;
import com.java2nb.novel.utils.NotifyIdempotentUtils;
import com.java2nb.novel.utils.OrderStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;

@Service
@Slf4j
public class PayNotifyServiceImpl implements PayNotifyService {
    @Autowired
    private PayOrderMapper payOrderMapper;
    @Autowired
    private MchAppConfigMapper mchAppConfigMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private NotifyIdempotentUtils notifyIdempotentUtils;
    @Autowired
    private RabbitTemplate rabbitTemplate; // 异步处理业务（如订单状态同步）

    public void handlePayNotify(PayNotifyRequestDTO requestDTO) {
        // 1. 幂等性校验：已处理过的通知直接返回
        if (notifyIdempotentUtils.checkPayNotifyProcessed(requestDTO.getPayOrderId())) {
            log.info("支付通知已处理，payOrderId={}", requestDTO.getPayOrderId());
            return;
        }

        // 2. 获取商户配置，验证签名
        MchAppConfig mchAppConfig = getMchAppConfig(requestDTO.getMchNo(), requestDTO.getAppId());
        if (mchAppConfig == null) {
            throw new RuntimeException("商户配置不存在，mchNo=" + requestDTO.getMchNo() + ", appId=" + requestDTO.getAppId());
        }
        // 转换请求为Map，验证签名
        Map requestMap = JSONObject.parseObject(JSON.toJSONString(requestDTO), Map.class);
        if (!SignUtils.verifySign(requestMap, mchAppConfig.getSecretKey(), requestDTO.getSign())) {
            throw new RuntimeException("支付通知签名验证失败，payOrderId=" + requestDTO.getPayOrderId());
        }

        // 3. 校验订单状态：仅处理待支付状态的订单
        PayOrder payOrder = payOrderMapper.selectOne(
                select(PayOrderDynamicSqlSupport.payOrderNo, PayOrderDynamicSqlSupport.mchNo, PayOrderDynamicSqlSupport.mchOrderNo,
                        PayOrderDynamicSqlSupport.appId, PayOrderDynamicSqlSupport.orderState)
                        .from(PayOrderDynamicSqlSupport.payOrder)
                        .where(PayOrderDynamicSqlSupport.payOrderNo, isEqualTo(requestDTO.getPayOrderId()))
                        .build()
                        .render(RenderingStrategies.MYBATIS3)
        );
        if (payOrder == null) {
            throw new RuntimeException("订单不存在，payOrderId=" + requestDTO.getPayOrderId());
        }
        if (!Objects.equals(payOrder.getOrderState(), OrderStatusEnum.PAY_PENDING.getCode())) {
            log.info("订单状态非待支付，无需处理，payOrderId={}, 当前状态={}",
                    requestDTO.getPayOrderId(), payOrder.getOrderState());
            // 标记为已处理（避免重复校验）
            notifyIdempotentUtils.markPayNotifyProcessed(requestDTO.getPayOrderId());
            return;
        }

        // 4. 更新订单状态（支付成功）
        payOrder.setOrderState(OrderStatusEnum.PAY_SUCCESS.getCode());
        payOrder.setPayTime(new Date(requestDTO.getCreatedAt()));
        payOrder.setChannelTrxId(requestDTO.getChannelTrxId());
        // 构建更新语句
        UpdateStatementProvider updateStatement = UpdateDSL.update(PayOrderDynamicSqlSupport.payOrder) // payOrder 是表名定义
                .set(PayOrderDynamicSqlSupport.orderState).equalTo(OrderStatusEnum.PAY_SUCCESS.getCode()) // 设置要更新的字段：例如将 status 改为 2
                .set(PayOrderDynamicSqlSupport.payTime).equalTo(new Date()) // 例如更新时间戳
                .where(PayOrderDynamicSqlSupport.payOrderNo, isEqualTo(payOrder.getPayOrderNo())) // 条件：id = 1001（必须加条件，否则更新全表！）
                .build()
                .render(RenderingStrategies.MYBATIS3); // 渲染为 MyBatis 可执行的语句

        int updateCount =  payOrderMapper.update(updateStatement);
        if (updateCount != 1) {
            throw new RuntimeException("更新订单状态失败，payOrderId=" + requestDTO.getPayOrderId());
        }

        // 5. 标记通知已处理（幂等性）
        notifyIdempotentUtils.markPayNotifyProcessed(requestDTO.getPayOrderId());

        // 6. 异步处理后续业务（如通知商户业务系统、记录日志）
        rabbitTemplate.convertAndSend("pay.notify.success.queue", JSON.toJSONString(payOrder));
        log.info("支付通知处理完成，payOrderId={}", requestDTO.getPayOrderId());
    }

    // 复用：获取商户配置
    private MchAppConfig getMchAppConfig(String mchNo, String appId) {
        String cacheKey = "pay:mch:config:" + mchNo + ":" + appId;
        String configJson = redisTemplate.opsForValue().get(cacheKey);
        if (configJson != null) {
            return JSONObject.parseObject(configJson, MchAppConfig.class);
        }
        MchAppConfig config = mchAppConfigMapper.selectOne(
                select(MchAppConfigDynamicSqlSupport.mchNo, PayOrderDynamicSqlSupport.mchNo, MchAppConfigDynamicSqlSupport.mchNo,
                        MchAppConfigDynamicSqlSupport.appId)
                        .from(MchAppConfigDynamicSqlSupport.mchAppConfig)
                        .where(MchAppConfigDynamicSqlSupport.mchNo, isEqualTo(mchNo))
                        .and(MchAppConfigDynamicSqlSupport.appId, isEqualTo(appId))
                        .build()
                        .render(RenderingStrategies.MYBATIS3)
        );
        if (config != null) {
            redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(config), 1, TimeUnit.HOURS);
        }
        return config;
    }
}